package team.seekers.hula.auth.filter;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import team.seekers.hula.auth.service.AuthService;
import team.seekers.hula.common.config.FilterProperties;
import team.seekers.hula.common.entity.UserSessionDTO;
import team.seekers.hula.common.entity.result.ApiExceptionEnum;
import team.seekers.hula.common.entity.enums.user.RoleEnum;
import team.seekers.hula.common.entity.result.ApiException;
import team.seekers.hula.common.utils.JwtUtil;
import team.seekers.hula.common.utils.NonExceptionOptional;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;

/**
 * @author GH
 */
@Slf4j
@Component
@EnableConfigurationProperties({FilterProperties.class})
public class LoginFilter extends OncePerRequestFilter implements Ordered {

    @Override
    public int getOrder() {
        return 0;
    }

    @Autowired
    private FilterProperties filterProp;

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Autowired
    private AuthService authService;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String requestUrl = request.getRequestURI().replace("/api", "");
        // 取 token
        String token = Optional.of(request)
                .map(map -> map.getHeader(UserSessionDTO.HEADER_KEY))
                .orElse(null);
        // 无(有效)token，非 allowUrl
        boolean isAllowPath = isAllowPath(requestUrl);
        if (!jwtUtil.checkToken(token) && !isAllowPath) {
            resolver.resolveException(request, response, null, new ApiException(ApiExceptionEnum.USER_NOT_LOGIN));
            return;
        }

        UserSessionDTO userSessionDTO = JwtUtil.getUserByToken(token);
        // 鉴权
        if (userSessionDTO != null) {
            List<String> urlRoles = NonExceptionOptional.ofNullable(() -> authService.urlToRoles(requestUrl))
                    .orElse(Lists.newArrayList());
            String role = NonExceptionOptional.ofNullable(userSessionDTO::getRole)
                    .orElse("");

            // 不为管理员，不为无权限限制的URL，不为白名单URL，且角色不符合URL角色集合
            if (!RoleEnum.ADMIN.getName().equals(role) && !urlRoles.contains(RoleEnum.ALL.getName()) && !urlRoles.contains(role) && !isAllowPath) {
                log.warn("无权限 {} {}", userSessionDTO, requestUrl);
                resolver.resolveException(request, response, null, new ApiException(ApiExceptionEnum.USER_NOT_MATCHING));
                return;
            }

            // 装饰器修改 Headers
            RequestHeaderWrapper decorator = new RequestHeaderWrapper(request);
//            decorator.removeHeader("cookie"); // 在此处已解码 token, 故不下传省流量, 如果后续有多值 cookie 此处需要修改
            decorator.removeHeader(UserSessionDTO.HEADER_KEY);
            decorator.addHeader(UserSessionDTO.HEADER_KEY, JSON.toJSONString(userSessionDTO));
            decorator.removeHeader(UserSessionDTO.KEY_USERID_PREFIX);
            decorator.addHeader(UserSessionDTO.KEY_USERID_PREFIX, userSessionDTO.getId());

            chain.doFilter(decorator, response);

            // 返回响应时检查是否注销
            Optional.ofNullable(response.getHeader(UserSessionDTO.HEADER_KEY))
                    .filter(UserSessionDTO.HEADER_VALUE_LOGOUT::equals)
                    .ifPresent(o -> jwtUtil.removeToken(userSessionDTO.getId()));
            return;
        }
        chain.doFilter(request, response);
    }

    private boolean isAllowPath(String requestUrl) {
        for (String allowPath : filterProp.getAllowPaths()) {
            if (requestUrl.startsWith(allowPath)) {
                return true;
            }
        }
        return false;
    }
}
